Skip to content

deps(wallet): gonuts-tollgate v0.15.0 — intents, NUT-09 restore, response binding - #1151

Closed
Amperstrand wants to merge 1 commit into
fix/degraded-error-honestyfrom
deps/gonuts-v0.15.0
Closed

Amperstrand wants to merge 1 commit into
fix/degraded-error-honestyfrom
deps/gonuts-v0.15.0

Conversation

@Amperstrand

Copy link
Copy Markdown
Collaborator

Summary

The fork release bump (v0.15.0 tagged 2026-10-28 after the autonomous gonuts window; PRs #33 #38 #39 #40 #42 all merged+verified there). scripts/bump-gonuts.sh v0.15.0 — manifest + every glob-discovered carrier in one run; fence green incl. --latest (newest stable tag).

What the fork version carries (live-verified on the fork side): mint+melt pending-op intents (the #497 replay invariant extended), NUT-09 /v1/restore fallback for 11001-refused swaps (#866), swap responses bound to the request (#831/#833), unpriceable-proof refusal (#832), spent-input retirement on every recovery path, integration-build fixture fix.

Evidence

make go-battery — 19 modules green on the stacked base. Downstream fixture impact: zero (the fork's tests are fork-local; tollgate's honest-fixture work landed already in #1063/#1132).

Stack: #1132 → #1136/#1140/this (any order among the three dependents; all rebase trivially). release-check note: with this merged, the pin = newest stable tag — no release gate conflict.

OWNER-GATED.

bump-gonuts.sh rewrite, all glob-discovered carriers + manifest together.
v0.15.0 delivers: mint+melt pending-op intents (crash recovery by
replay), NUT-09 restore fallback for 11001-refused swaps, swap-response
binding (amount+keyset by index), unpriceable-proof refusal, spent-input
retirement on recovery, integration-build fixture fix. Full-suite
evidence on the fork: 12 packages ok; here: 19 modules green on the
stacked base; fence green incl. --latest (v0.15.0 = newest stable tag).
@Amperstrand

Copy link
Copy Markdown
Collaborator Author

Local CI gate — PR #1151: deps(wallet): gonuts-tollgate v0.15.0 — intents, NUT-09 restore, response binding

Run of scripts/pr-verify.sh (the local CI gate: GitHub Actions is
org-blocked and the ngit lane fires only on maintainer mirror pushes,
so a PR ref gets no CI runs — this comment is the offline verification
evidence, produced on the pinned head SHA).

  • Head: deps/gonuts-v0.15.0 @ 29efd1c2d3
  • Base: main @ 6aabea2d36
  • Gate: pr-verify 294317251383 on ai-legion, started 2026-10-30 21:57 UTC, wall 695s
  • Logs: /tmp/tollgate-pr-verify/1151-20261030T214552Z (on the verifying host)
Leg Result Detail
hygiene PASS no conflict markers in the tracked tree (0s)
contract-fences FAIL red: changelog fragments (assemble check) doc-facts contract dependency sync — see contract-fences.log; 13 checks green (12s)
packaging-sequential FAIL red (3/17): backend-api-owner-network_test.sh entry-ui-install-sequence_test.sh rebrand-literal-gutter_test.sh — logs in /tmp/tollgate-pr-verify/1151-20261030T214552Z/packaging-sequential (24s)
shell-suites FAIL red (2/23): uci-defaults-gatewayname-banner_test.sh uci-defaults-lan-private-wired_test.sh — logs in /tmp/tollgate-pr-verify/1151-20261030T214552Z/shell-suites (103s)
packaging-concurrent FAIL red (3/17): backend-api-owner-network_test.sh entry-ui-install-sequence_test.sh rebrand-literal-gutter_test.sh — logs in /tmp/tollgate-pr-verify/1151-20261030T214552Z/packaging-concurrent (10s)
go-battery PASS 19 modules, gofmt/vet/build/-race (546s)

Verdict: 4 leg(s) RED — 2 green.

What this gate does not run (no vacuous green)
  • spec-quote drift check (greatspectations tool + pinned nuts fetch)
  • reproducibility / conformance lanes (make release-check territory)
  • router-visible behaviour (PRTA / bench — unit green ≠ router works)
failing-leg log tails
contract-fences — tail

🔴 google.golang.org/genproto/googleapis/rpc — 2 versions:
   root: v0.0.0-20260928230214-8a89bd6388cc
   tollwallet: v0.0.0-20260720211330-0afa2a65878a

🔴 google.golang.org/grpc — 2 versions:
   root: v1.83.2
   tollwallet: v1.82.1

Fix: update all go.mod files to the same version.
     Run 'go mod tidy' in each sub-module after updating.
== gonuts pin fence (offline identity)
ok   an aligned tree (block + single-line carriers) passes
ok   a block-form carrier on a different version
ok   a single-line carrier left behind (the token-recovery shape)
ok   a go.sum that never recorded the truth version (half-update)
ok   a self-consistent pseudo-version integration pin passes OFFLINE (the --latest leg is the release gate for it)
ok   a manifest without the .gonuts key
ok   a tree whose manifest names a pin no carrier has

7 cases, 0 failed
== gonuts pin carriers
  manifest pin (packaging/build-inputs.json .gonuts.version): v0.15.0
  carriers discovered by glob: 5 module(s)
✅ one pin, everywhere: github.com/OpenTollGate/gonuts-tollgate v0.15.0 across 5 carrier(s).
== mirror/relay sync
ok    BLOSSOM_SERVERS: 13 copies identical (https://blossom.primal.net https://blossom.psbt.me https://blossom2.orangesync.tech)
ok    relay list: 13 copies identical (wss://relay.damus.io wss://nos.lol wss://nostr.mom wss://relay2.orangesync.tech)
== stale import paths
✅ All 358 Go source files use correct import paths.
packaging-sequential — tail
--- backend-api-owner-network_test.sh.log
ok   the fragment guards exactly :2121
ok   packaging/files/etc/nftables.d/30-backend-firewall.nft is an include fragment (no own table declaration)
ok   the chain hooks input at priority -1 (the drop is authoritative)
ok   chain 'backend_input_firewall' is declared once
== the admin-board and LuCI guards are still captive-bridge-scoped
ok   31-admin-board-not-guest-reachable.nft is scoped by $tg_portal_if (the captive bridge's define)
ok   32-luci-not-guest-reachable.nft is scoped by $tg_portal_if (the captive bridge's define)
== the recipe installs the rule
ok   local-build-ipk.sh installs 30-backend-firewall.nft
ok   packaging/Makefile installs the 30-backend-firewall.nft rule via its *.nft glob
ok   packaging/Makefile FILES_ lists /etc/nftables.d/30-backend-firewall.nft
== the fragment parses
FAIL nft -c rejects the fragment — 'nft -f' would fail the whole fw4 reload

passed=17 failed=1
--- entry-ui-install-sequence_test.sh.log

== scenario 4b: converged box, root hash emptied — 999 re-refuses the board
ok   scenario 4b: :8080 is unbound — the refused board's own pair
ok   scenario 4b: :443 is unbound — the refused board's own pair
ok   scenario 4b: exactly one section binds :8090 (main)
ok   scenario 4b: exactly one section binds :8443 (main)
ok   scenario 4b: uhttpd.main keeps at least one listener
ok   scenario 4b: uhttpd.portal keeps at least one listener
ok   scenario 4b: uhttpd.trusted keeps at least one listener
ok   scenario 4b: the admin board is deliberately refused (fail-closed gate), not torn
ok   scenario 4b: LuCI (uhttpd.main) answers :8090 on both families
ok   scenario 4b: LuCI (uhttpd.main) answers :8443 on both families
ok   scenario 4b: the board is refused — its pair is intentionally unbound

tests: 72 passed, 1 failed
--- rebrand-literal-gutter_test.sh.log

== negative control: the scan pattern sees a planted occurrence
ok   the pattern matches a planted occurrence (the gutter is not inert)
ok   the pattern does not match a near-miss brand name (it is anchored to the banned literal)
ok   scan_tree ignores the control file (it is outside the tracked tree)

== the shipped uci-defaults reference only known uhttpd sections
ok   every uhttpd.<name> in the uci-defaults' active code is a known section (or a cert/key path)

== control: the section tokenizer ignores comments, not code
ok   comment-only section names (full-line and trailing) are not flagged
ok   known sections in active code are still extracted
ok   a foreign section in active code is still extracted (the check keeps its teeth)

tests: 8 passed, 1 failed
shell-suites — tail
--- uci-defaults-gatewayname-banner_test.sh.log
ok   the FULL path's banner IS the canonical one from the shared table
ok   reinstall (same version) run exits 0
ok   the same-version reinstall took the VERIFY branch
ok   the repair path COMMITS the banner '!TollGate-OQ3Q Portal'
ok   the repair path's banner is the SAME value the full path writes
ok   no gatewayname op is left pending in the /tmp/.uci-style delta
FAIL the repair path's captive SSID converged on the same code (got '!tollgate-0GLK', want '!TollGate-OQ3Q')
FAIL the banner is exactly '<captive SSID> Portal' (got '!TollGate-OQ3Q', want '!tollgate-0GLK')
ok   the operator's PSK is untouched
ok   drifting reinstall run exits 0
ok   the drifting repair path commits the SAME banner
ok     ...and the drifted allow-list rule was repaired (the commit did fire)
ok     ...with no gatewayname op left pending

27 passed, 2 failed
--- uci-defaults-lan-private-wired_test.sh.log
ok   silent no-op: the log names the missing port
== empty source and empty target: reported as an error, nothing written
ok   empty source+target: writer returns non-zero (1)
ok   empty source+target: private_bridge still has no ports
ok   empty source+target: log reports the broken state
== bridge device section beyond index 23 is still found and moved
ok   late bridge: writer returns 0
ok   network.private_bridge lists eth1 exactly once
ok   late bridge: the source section was cleared
== negative control: a script with a no-op writer fails the assertions
ok   negative control: the writer is neutralised in the copied script
ok   negative control: without the writer the port stays on the captive bridge
ok   negative control: the captive bridge keeps its port (the move assertion would fail)

lan-private-wired: 8 check(s) FAILED, 35 passed
packaging-concurrent — tail
--- backend-api-owner-network_test.sh.log
ok   the fragment guards exactly :2121
ok   packaging/files/etc/nftables.d/30-backend-firewall.nft is an include fragment (no own table declaration)
ok   the chain hooks input at priority -1 (the drop is authoritative)
ok   chain 'backend_input_firewall' is declared once
== the admin-board and LuCI guards are still captive-bridge-scoped
ok   31-admin-board-not-guest-reachable.nft is scoped by $tg_portal_if (the captive bridge's define)
ok   32-luci-not-guest-reachable.nft is scoped by $tg_portal_if (the captive bridge's define)
== the recipe installs the rule
ok   local-build-ipk.sh installs 30-backend-firewall.nft
ok   packaging/Makefile installs the 30-backend-firewall.nft rule via its *.nft glob
ok   packaging/Makefile FILES_ lists /etc/nftables.d/30-backend-firewall.nft
== the fragment parses
FAIL nft -c rejects the fragment — 'nft -f' would fail the whole fw4 reload

passed=17 failed=1
--- entry-ui-install-sequence_test.sh.log

== scenario 4b: converged box, root hash emptied — 999 re-refuses the board
ok   scenario 4b: :8080 is unbound — the refused board's own pair
ok   scenario 4b: :443 is unbound — the refused board's own pair
ok   scenario 4b: exactly one section binds :8090 (main)
ok   scenario 4b: exactly one section binds :8443 (main)
ok   scenario 4b: uhttpd.main keeps at least one listener
ok   scenario 4b: uhttpd.portal keeps at least one listener
ok   scenario 4b: uhttpd.trusted keeps at least one listener
ok   scenario 4b: the admin board is deliberately refused (fail-closed gate), not torn
ok   scenario 4b: LuCI (uhttpd.main) answers :8090 on both families
ok   scenario 4b: LuCI (uhttpd.main) answers :8443 on both families
ok   scenario 4b: the board is refused — its pair is intentionally unbound

tests: 72 passed, 1 failed
--- rebrand-literal-gutter_test.sh.log

== negative control: the scan pattern sees a planted occurrence
ok   the pattern matches a planted occurrence (the gutter is not inert)
ok   the pattern does not match a near-miss brand name (it is anchored to the banned literal)
ok   scan_tree ignores the control file (it is outside the tracked tree)

== the shipped uci-defaults reference only known uhttpd sections
ok   every uhttpd.<name> in the uci-defaults' active code is a known section (or a cert/key path)

== control: the section tokenizer ignores comments, not code
ok   comment-only section names (full-line and trailing) are not flagged
ok   known sections in active code are still extracted
ok   a foreign section in active code is still extracted (the check keeps its teeth)

tests: 8 passed, 1 failed

@Amperstrand

Copy link
Copy Markdown
Collaborator Author

Two coordination notes from the campaign review pass:

  1. This branch commits the built binary src/cmd/tollgate-ndsctl/tollgate-ndsctl (a review found the same stray artifact in fix(merchant): degraded-mode hygiene — startup banner names the real recovery path; dead error seam removed (#1135) #1140 — it keeps recurring across branches; worth a gitignore entry or an X-Check). Recommend dropping it from this PR.
  2. Ordering with feat(merchant): wallet.backend selection wired through the OpenWallet seam #1155 (wallet.backend wiring + module e2e): feat(merchant): wallet.backend selection wired through the OpenWallet seam #1155 does not touch the gonuts pin, so either order works; but landing this bump BEFORE feat(merchant): wallet.backend selection wired through the OpenWallet seam #1155 lets the campaign's five-leg e2e evidence cover the new fork version too — the legs run gonuts through the default backend. If it lands after, a one-line e2e re-run against v0.15.0 would keep the matrix claim honest.

@Amperstrand

Copy link
Copy Markdown
Collaborator Author

CLOSING in favor of #1175 — same-house duplicate reconciliation (both v0.15.0 bumps; #1175 opened today).

Both branches pass check-gonuts-pin.py (one pin v0.15.0 across the 5 glob-discovered carriers — verified locally on both heads). The delta:

#1175 is the same bump with better hygiene. Closing this one; nothing unique is lost.

@felixfelix-bot

Copy link
Copy Markdown
Collaborator

BLOCK — this PR commits a compiled binary to the repo

src/cmd/tollgate-ndsctl/tollgate-ndsctl is in the diff as a new file. It is not source:

$ git cat-file -s sw1151:src/cmd/tollgate-ndsctl/tollgate-ndsctl
3674440                                    # 3.5 MB

$ git cat-file -p sw1151:src/cmd/tollgate-ndsctl/tollgate-ndsctl | head -c 20 | file -
ELF 64-bit LSB executable, x86-64, (SYSV)
$ git diff --name-status upstream/main...sw1151 -- src/cmd/tollgate-ndsctl/tollgate-ndsctl
A   src/cmd/tollgate-ndsctl/tollgate-ndsctl

It is a new file on this branch and is not tracked on main, so merging makes it permanent history.

Why it matters for this repo specifically:

  • It bakes in one architecture. The module ships mips/mipsel/arm/arm64/x86 (CGO_ENABLED=0 cross-builds). An x86-64 ELF in the tree is meaningless to five of those six targets and will be silently rebuilt over by anyone running the battery.
  • It cannot be reviewed. No human or agent can diff a 3.5 MB blob; the adjacent fix(merchant): degraded-mode hygiene — startup banner names the real recovery path; dead error seam removed (#1135) #1140 (fix(merchant)) shows how easily it slips through — its file list carries the same blob with +0/-0 and nothing flags it.
  • It races the battery. src/cmd/tollgate-ndsctl/ builds to that exact path, so the tracked copy is overwritten on every local build and shows up as a spurious modification — which is exactly the state that produces "works here" confusion in make go-battery.

#1180 (chore: gitignore the cmd modules' built binaries) is the right cleanup for the ignored path, but a .gitignore line does nothing once a blob is tracked — git still tracks it. This one needs git rm --cached on this branch.

Fix on this branch:

git rm --cached src/cmd/tollgate-ndsctl/tollgate-ndsctl
git commit -m 'chore: do not track the built tollgate-ndsctl binary'

Then #1180's ignore rule actually takes effect. I have not pushed to this branch — happy to open a stacked PR against it if you prefer that to editing it yourself.

Everything else about the pin bump looked right when I swept it: check-gonuts-pin.py green across the carriers it discovers, go build/go vet clean. This is the one blocker.

@felixfelix-bot felixfelix-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes — one blocker: a compiled binary is committed in this PR.

src/cmd/tollgate-ndsctl/tollgate-ndsctl is a new 3.5 MB x86-64 ELF in the diff, not tracked on main. Merging welds it into history permanently, makes five of the six shipped architectures wrong to look at, and gets overwritten by every local build of that module. git rm --cached it on the branch before this can land; details and the exact command are in my comment above.

The pin bump itself checks out — I swept it and the offline fence is green:

$ python3 tests/contract/check-gonuts-pin.py           # on sw1151
✅ one pin, everywhere … across the carriers it discovers by glob

$ cd src && go build ./...    → rc=0
$ cd src && go vet ./...      → rc=0

Also worth noting for sequencing: this PR and #1142 both move the gonuts pin to the EOF-tolerant version, and my #1182 does the same for the manifest plus all carriers. #1142's version only moves src/tollwallet/go.mod, which breaks cd src && go build ./.... Whichever of the three lands first should be the one that moves the manifest, or the next two re-tread the same lines.

@felixfelix-bot

Copy link
Copy Markdown
Collaborator

Correction to my own evidence — the byte count, not the finding.

My review cited the committed binary as 3,674,440 bytes. That number is wrong. Re-measured at the actual HEAD (29efd1c2):

$ git cat-file -s 29efd1c2:src/cmd/tollgate-ndsctl/tollgate-ndsctl
3620835

3,620,835 bytes (≈3.45 MiB), not 3,674,440. I also confirmed it is a single introduction — this is the only commit on the branch touching that path, so there is no earlier 3,674,440 version I could have been measuring:

$ git rev-list 29efd1c2 --not upstream/main -- src/cmd/tollgate-ndsctl/tollgate-ndsctl
29efd1c2d3d07e91c7e1475db8909c1ad511512e 3620835

The conclusion is unchanged, and re-verified:

$ git cat-file -p 29efd1c2:src/cmd/tollgate-ndsctl/tollgate-ndsctl | head -c 4 | od -c
0000000 177   E   L   F

So it is a committed ELF executable at src/cmd/tollgate-ndsctl/tollgate-ndsctl, it is absent from main (git cat-file -s b4f15513:… → fatal: path … does not exist), and merging this branch would put a ~3.45 MiB platform-specific binary into the repository permanently.

Why I am correcting rather than letting it stand

A wrong number inside a correct finding is worse than no number: the next person who runs git cat-file -s and gets 3620835 has to decide whether I measured the right thing, whether the branch moved, or whether the whole finding is sloppy. The measurement is the part I asked to be believed on, so it has to be exactly right.

Correcting my earlier comment from "3.5 MB (3674440 bytes)" to 3,620,835 bytes. The CHANGES_REQUESTED stands unchanged: drop the binary from the tree (rebuild it from source at install time — the branch already contains the source it was built from) and this clears.

Also worth noting for whoever picks this up

The branch's actual purpose is the gonuts-tollgate v0.14.1 → v0.15.0 bump. That bump is worth a separate look on its own merits, but it cannot be judged while a 3.45 MiB blob rides along in the same diff — which is the other reason to split it out rather than wave it through.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants